/**
 * @file simple_message_client.c
 * Verteilte Systeme - TCP/IP Programmierübung
 *
 * tcpip client
 *
 * @author Theresa Meiksner - theresa.meiksner@technikum-wien.at
 * @author Ronald Wimmer    - ronald.wimmer@technikum-wien.at
 * @date 2013/12/09
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <netdb.h>
#include <unistd.h>
#include "simple_message_client_commandline_handling.h"

/* maximum client output stream size */
#define MAX_OUTPUT_SIZE 256
/* maximum client input stream size */
#define MAX_INPUT_SIZE 8192

/* maximum size for the filename attribute in the response */
#define MAX_FILE_NAME_SIZE 256
/* maximum size for the length attribute in the response */
#define MAX_LENGTH_SIZE 10

/**
 * \brief method for finding the attribute value of the given attribute key
 * in the given byte array
 *
 * \param data - the byte array, where we want to search in
 * \param key - the attribute key to be searched
 * \param value - the found attribute value

 * \return char* pointer
 * \retval the pointer, after the found value, so we can search the next item
 *
 */
char* getValueFromByteArray(char* data, const char *key, char *value);

/**
 * \brief error handling routine
 *
 * \param location - info about the location, where the error happened
 * \param errorMessage - additional information about the specific error
 *
 */
void showErrorMessageAndExit(char* location, const char* errorMessage);

/**
 * \brief method for writing the files from the server response byte array
 *
 * \param input_data - the byte array from the server
 */
void writeFiles(char* input_data);

/**
 * \brief function for: smc_parsecommandline, see the doc of this function for further
 * information
 *
 * \param stream - the stream to write
 * \param message - the message to write
 * \param exitcode - the exitcode
 */
void showUsage(FILE * stream, const char * message, int exitcode);

 /**
  * \brief this function reads the response from the server in the byte array
  *
  * \param input_data - the byte array to be filled from the response
 */
void readData(char *input_data);

/**
 * \brief this function sends the request to the server
 *
 */
void submitMessage();

/**
 * \brief only one simple send would not suffice, we must send in a loop
 *
 * \param sock_fd - the socket handle
 * \param buf - buffer to write
 * \param len - the length of the message
 */
int sendAll(int sock_fd, char *buf, int *len);


/* socket handle */
int socketFileDescriptor = 0;
/* file handle for output files written from the server response stream */
FILE *output_file_fd = NULL;
/* program name for error handling */
const char *programName = NULL;
/* client argument values*/
const char *server, *port, *user, *message, *image;
int verbose = 0;
char verboseMessage[256];

/* macro for verbose */
#define verbose(count) \
if (verbose != 0) \
{ \
	if (count >= 0) \
	{ \
	  	printf("%s [%s, %s, line %d]: %s\n", programName, __FILE__, __FUNCTION__,  __LINE__, verboseMessage); \
		memset(verboseMessage, 0, sizeof(verboseMessage)); \
	} \
}

int main(int argc, const char * const argv[]) {

	/* the byte array for the request, sent to the server */
	char input_data[MAX_INPUT_SIZE];

	programName = argv[0];

	/* get the arguments, via the provided library */
    smc_parsecommandline(argc, argv, &showUsage, &server, &port, &user, &message, &image, &verbose);

    verbose(sprintf(verboseMessage, "using arguments server: %s, port: %s, user: %s, message: %s, image: %s", server, port, user, message, image));

    /* submit the message to the server */
    submitMessage();

    /* receive the request */
    readData(input_data);

	/* write the files from the response - error handling happens in the method */
	writeFiles(input_data);

	verbose(sprintf(verboseMessage, "client sucessfully finished !"));

	return 0;
}


void readData(char *input_data)
{
	int bytes_read = 0;
/*	   struct timespec tim, tim2; */

    verbose(sprintf(verboseMessage, "calling shutdown..."));

    /* calling shutdown write, client can still receive data */
	if (shutdown(socketFileDescriptor, SHUT_WR) != 0)
	{
		showErrorMessageAndExit("shutdown()", "error while calling shutdown");
	}

	verbose(sprintf(verboseMessage, "receiving data from socket..."));

    /* read response stream from server into input_data array
     * for this kind of server and response the flag MSG_WAITALL should
     * suffice ... in an environment with larger responses, we should
     * call recv in a loop and read the data in chunks */
	bytes_read = recv(socketFileDescriptor, input_data, MAX_INPUT_SIZE, MSG_WAITALL);
	verbose(sprintf(verboseMessage, "%d bytes read from socket...", bytes_read));

	if (bytes_read <=0)
	{
		showErrorMessageAndExit("recv()", "error while receiving data from socket");
	}

	verbose(sprintf(verboseMessage, "closing socket with handle: %d", socketFileDescriptor));

	if (close(socketFileDescriptor) != 0)
	{
		  showErrorMessageAndExit("close()", "error while closing socket");
	}

}

void submitMessage()
{
	/* the byte array for the server response */
	char output_data[MAX_OUTPUT_SIZE];
	/* structures for socket handling */
	struct addrinfo hints, *socket_address;
	int status = 0, count, length;

	/* initialize the structure */
	memset(&hints, 0, sizeof hints);

	/* UNSPEC = ipv4 and ipv6*/
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

    verbose(sprintf(verboseMessage, "get address info"));

	/* get address info struct */
	if ((status = getaddrinfo(server, port, &hints, &socket_address)) != 0) {
		showErrorMessageAndExit("getaddrinfo", gai_strerror(status));
	}

    verbose(sprintf(verboseMessage, "calling socket()"));

	if ((socketFileDescriptor = socket(socket_address->ai_family,
		 socket_address->ai_socktype, socket_address->ai_protocol)) == -1) {
		showErrorMessageAndExit("socket()", "error while calling socket()");
	}

    verbose(sprintf(verboseMessage, "got socket handle: %d", socketFileDescriptor));

    verbose(sprintf(verboseMessage, "calling connect on: %s:%s", server, port));

    if (connect(socketFileDescriptor, socket_address->ai_addr, socket_address->ai_addrlen) == -1) {
		showErrorMessageAndExit("connect()", "error while calling connect()");
	}

    verbose(sprintf(verboseMessage, "freeing the socket address..."));
	/* free the structure - not needed anymore */
	freeaddrinfo(socket_address);

    verbose(sprintf(verboseMessage, "building request message..."));

	/* if the optional i argument is given, the text to be sent is different*/
	if (image == NULL)
		count = sprintf(output_data, "user=%s\n%s\n", user, message);
	else
		count = sprintf(output_data, "user=%s\nimg=%s\n%s\n", user, image, message);

    verbose(sprintf(verboseMessage, "sending request message: %s", output_data));

	if (count < 0) {
		showErrorMessageAndExit("sprintf()", "error while calling sprintf");
	}

	length = strlen(output_data);

	if (sendAll(socketFileDescriptor, output_data, &length) == -1) {
		showErrorMessageAndExit("sendAll()", "error while sending text to server");
	}

}

void writeFiles(char* input_data)
{
	/* This are the keys for reading the attributes from the server response stream */
	const char *FILE_KEY = "file=";
	const char *STATE_KEY = "status=";
	const char *LENGTH_KEY = "len=";

	/* variables for the attribute values */
	char length[MAX_LENGTH_SIZE];
	long length_num = 0;
	char filename[MAX_FILE_NAME_SIZE];
	char business_logic_state[1];

	/* pointer for processing response byte array */
	char *pch = NULL;

	/* get the business logic state out of the response stream */
	pch = getValueFromByteArray(input_data, STATE_KEY, business_logic_state);

	/* Business logic state bit is set to 0 => means OK*/
	if (business_logic_state[0] == '0')
	{
	    verbose(sprintf(verboseMessage, "server call = OK..."));

	    /* read until we find no more "file" key in the stream*/
		while (pch != NULL)
		{
			/* get the file name*/
			pch = getValueFromByteArray(pch, FILE_KEY, filename);
			if (pch != NULL)
			{
				/* get the file length */
				pch = getValueFromByteArray(pch, LENGTH_KEY, length);
	            if (pch == NULL)
				{
	        		showErrorMessageAndExit("getValueFromByteArray()", "error while getting length value from response stream");
				}

	    	    verbose(sprintf(verboseMessage, "opening file in write mode: %s", filename));
	            /* open the file with the given filename in write mode */
	           	if ((output_file_fd = fopen(filename, "w")) == NULL )
	           	{
	        		showErrorMessageAndExit("fopen()", filename);
	           	}

	           	/* converting in to a numeric type */
	           	if ((length_num = strtol(length, NULL, 0)) == 0)
	           	{
	        		showErrorMessageAndExit("strtol()", length);
	           	}

	           	verbose(sprintf(verboseMessage, "writing bytes to file: %s", filename));

	           	/* write the byte array to the file */
	           	if (fwrite (pch , 1 , length_num , output_file_fd ) == 0)
	           	{
	        		showErrorMessageAndExit("fwrite()", filename);
	           	}

	           	verbose(sprintf(verboseMessage, "%ld bytes written to file: %s", length_num, filename));
	           	verbose(sprintf(verboseMessage, "closing file: %s", filename));

	           	/* close the file */
	           	if (fclose (output_file_fd ) == EOF)
	           	{
	        		showErrorMessageAndExit("fwrite()", filename);
	           	}
	           	verbose(sprintf(verboseMessage, "file: %s closed successfully", filename));
			}

 		}

	}
	else
	{
       	verbose(sprintf(verboseMessage, "business logic call = NOT OK, response = %s", input_data));
		showErrorMessageAndExit("response error: ", input_data);
	}

}

char * getValueFromByteArray(char* data, const char *key, char *value)
{
    char *nch;
	char *ptr;

	verbose(sprintf(verboseMessage, "getting attribute with key '%s' from response byte array", key));

	if (data == NULL)
	{
	   verbose(sprintf(verboseMessage, "inputstream is null, key is: %s", key));
   	   return NULL;
	}

	/* initialize the value */
	memset (value, 0, sizeof(value) );

	/* search for the given key */
	ptr = strstr(data, key);

	/* not found */
	if (ptr == NULL)
	{
	   verbose(sprintf(verboseMessage, "key not found: %s", key));
   	   return NULL;
	}

	/* found the key -> set the pointer after the '=' character */
	ptr = ptr + strlen(key);

	/* determine the new line position */
	nch = strchr(ptr, '\n');

	/* no new line found => error in format */
	if (nch == NULL)
	{
	   verbose(sprintf(verboseMessage, "no new line found for attribute value with key: %s", key));
   	   return NULL;
	}

	/* copy the found value in to the give value variable */
	strncpy(value, ptr, nch - ptr);

	/* position the pointer after the value, so we can search the next item */
	ptr = ptr + strlen(value) + 1;

    verbose(sprintf(verboseMessage, "attribute value of key '%s' is: %s", key, value));

	return ptr;
}

void showErrorMessageAndExit(char* location, const char* errorMessage)
{
	/* No error handling here, because we are already in the error handling routine */
	/*if (socket_fd > 0)
		close(socket_fd);

	if (output_file_fd != NULL)
		fclose(output_file_fd);
*/
	fprintf(stderr, "last errno: \n");
	perror(programName);
	fprintf(stderr, "Error occured in - %s: %s\n", location, errorMessage);
	exit(EXIT_FAILURE);
}

void showUsage(FILE * stream, const char * message, int exitcode)
{
 	fprintf(stream, message);
	exit(exitcode);
}

/* thanks to beej */
int sendAll(int s, char *buf, int *len) {
	int total = 0;
	int bytesleft = *len;
	int n;

	while (total < *len) {
		n = send(s, buf + total, bytesleft, 0);
		if (n == -1) {
			break;
		}
		total += n;
		bytesleft -= n;
	}
	*len = total;
	return n == -1 ? -1 : 0;
}

